/*
 * Copyright @ 2010 Radomir Mladenovic and BugsIO Solutions
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.gjutil.caching.provider;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.googlecode.gjutil.caching.CachedItemHolder;

public class SimpleCache implements Serializable {
	private static final long serialVersionUID = 1L;
	
	// SoftReference is not Serializable so we use custom serialization
	private transient HashMap<Object, SoftReference<CacheItem>> cache = new HashMap<Object, SoftReference<CacheItem>>();
	
	private static class CacheItem implements CachedItemHolder, Serializable {
		private static final long serialVersionUID = 1L;
		
		long expirationTime;
		Object value;
		
		public Object getValue() {
			return value;
		}
	}
	
	public void put(Object key, Object value) {
		put(key, value, -1);
	}
	
	public synchronized void put(Object key, Object value, int expirationSeconds) {
		CacheItem item = new CacheItem();
		item.value = value;
		if(expirationSeconds > 0)
			item.expirationTime = System.currentTimeMillis() + 1000 * expirationSeconds;
		cache.put(key, new SoftReference<CacheItem>(item));
	}
	
	public synchronized CachedItemHolder get(Object key) {
		SoftReference<CacheItem> ref = cache.get(key);
		if(ref == null)
			return null;
		CacheItem item = ref.get(); 
		if(item != null && item.expirationTime < System.currentTimeMillis()) {
			cache.remove(key);
			return null;
		}
		return item;		
	}
	
	public synchronized void cleanup() {
		for(Iterator<Map.Entry<Object, SoftReference<CacheItem>>> it = cache.entrySet().iterator(); it.hasNext(); ) {
			Map.Entry<Object, SoftReference<CacheItem>> entry = it.next();
			SoftReference<CacheItem> ref = entry.getValue();
			if(ref == null)
				continue;
			CacheItem cacheItem = ref.get();
			if(cacheItem == null || cacheItem.expirationTime < System.currentTimeMillis()) {
				it.remove();
				//System.err.println("Removed from cache key " + entry.getKey());
	        }
		}
	}
	
	public synchronized void clear() {
		cache.clear();
	}
	
	private void writeObject(ObjectOutputStream out) throws IOException	{
		out.defaultWriteObject();
		HashMap<Object, CacheItem> wcache = new HashMap<Object, CacheItem>();
		for(Iterator<Map.Entry<Object, SoftReference<CacheItem>>> it = cache.entrySet().iterator(); it.hasNext(); ) {
			Map.Entry<Object, SoftReference<CacheItem>> entry = it.next();
			SoftReference<CacheItem> ref = entry.getValue();
			if(ref == null)
				continue;
			CacheItem cacheItem = ref.get();
			if(cacheItem != null && cacheItem.expirationTime > System.currentTimeMillis()) {
				wcache.put(entry.getKey(), cacheItem);
				//System.err.println("Prepared for serialization cache key " + entry.getKey());
	        }
		}
		out.writeObject(wcache);
	}
	
	@SuppressWarnings("unchecked")
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		in.defaultReadObject();
		HashMap<Object, CacheItem> wcache = (HashMap<Object, CacheItem>) in.readObject();
		cache = new HashMap<Object, SoftReference<CacheItem>>();
		for(Iterator<Map.Entry<Object, CacheItem>> it = wcache.entrySet().iterator(); it.hasNext(); ) {
			Map.Entry<Object, CacheItem> entry = it.next();
			cache.put(entry.getKey(), new SoftReference<CacheItem>(entry.getValue()));
			//System.err.println("De-serialized cache key " + entry.getKey());
		}
	}	
}
